package com.jsjvm.server.dbgproxy;

import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map.Entry;

import com.jsjvm.server.dbgproxy.JDWPCommandSet.JDWPCommandHandler;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPArrayReferenceCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPArrayTypeCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPClassLoaderCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPClassObjectCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPClassTypeCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPEventRequestCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPMethodCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPObjectReferenceCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPReferenceTypeCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPStackFrameCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPStringCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPThreadGroupCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPThreadReferenceCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.cmdsets.JDWPVirtualMachineCommandSet;
import com.jsjvm.server.dbgproxy.jdwp.data.JDWPInputStream;
import com.jsjvm.server.dbgproxy.jdwp.data.JDWPOutputStream;
import com.jsjvm.server.dbgproxy.jdwp.data.JDWPRequestHeader;
import com.jsjvm.shared.jdwp.events.JDWPEvent;
import com.jsjvm.shared.jdwp.events.JDWPVMStartEvent;
import com.jsjvm.shared.vmcmds.VMCommand;

/**
 * This class acts like a back-end as defined in JDPA.
 * 
 * It listens for messages from the debugger front-end over a JDWP TCP
 * connection and communicates through AJAX with the JSJVM running in the
 * browser.
 * 
 */
public class JDPABackEnd {
	public static final int THREAD_ID = 1;
	public static final int THREAD_GROUP_ID = 2;
	public static final int CLASS_LOADER_ID = 3;
	
	public static final String THREAD_NAME = "THE-THREAD";
	public static final String THREAD_GROUP_NAME = "THE-THREAD-GROUP";
	
	private Socket sock;
	private JDWPInputStream dis;
	private JDWPEventOutputStream dos;

	public JDPABackEnd(int port) throws IOException {
		sock = new Socket("127.0.0.1", port);
		dis = new JDWPInputStream(sock.getInputStream());
		dos = new JDWPEventOutputStream(sock.getOutputStream());
		registerCommandSets();
	}

	// Handlers for command sets defined in the specification
	private static JDWPCommandSet cmdSets[] = {
			new JDWPArrayReferenceCommandSet(), 
			new JDWPArrayTypeCommandSet(),
			new JDWPClassLoaderCommandSet(), 
			new JDWPClassObjectCommandSet(),
			new JDWPClassTypeCommandSet(), 
			new JDWPEventRequestCommandSet(),
			new JDWPMethodCommandSet(), 
			new JDWPObjectReferenceCommandSet(),
			new JDWPReferenceTypeCommandSet(), 
			new JDWPStackFrameCommandSet(),
			new JDWPStringCommandSet(), 
			new JDWPThreadGroupCommandSet(),
			new JDWPThreadReferenceCommandSet(),
			new JDWPVirtualMachineCommandSet(), 
	};
	// map from commandset:command to the actual handler
	private HashMap<Integer, JDWPCommandHandler> handlers = new HashMap<Integer, JDWPCommandHandler>();

	// build the datastructure for easy dispatching the needed handler
	private void registerCommandSets() {
		for (JDWPCommandSet cmdSet : cmdSets) {
			int cmdSetNo = cmdSet.getCommandSetNumber();
			for (Entry<Integer, JDWPCommandHandler> cmd : cmdSet.entrySet()) {
				handlers.put((cmdSetNo << 8) + cmd.getKey(), cmd.getValue());
			}
		}
		registerHarcodedHandlers();
	}

	private void registerHarcodedHandlers() {
		// dispose handler
		int vmcs = JDWPVirtualMachineCommandSet.VIRTUAL_MACHINE_SET;
		int disp = JDWPVirtualMachineCommandSet.DISPOSE_CMD;
		handlers.put((vmcs << 8) + disp, new JDWPCommandHandler() {
			@Override
			public VMCommand handleCommand(JDWPRequestHeader hdr,
					JDWPInputStream is, JDWPOutputStream os) throws IOException {
				JDPABackEnd.this.exitRequested = true;
				return null;
			}
		});
	}

	// retrieve the suitable handler for some command
	private JDWPCommandHandler dispatchHandler(JDWPRequestHeader hdr) {
		int cmdSet = hdr.getCommandSet();
		int cmd = hdr.getCommand();
		return handlers.get((cmdSet << 8) + cmd);
	}

	// performs the handshake of the JDWP protocol
	public void handshake()
			throws IOException {
		byte buf[] = new byte[14];
		dos.write("JDWP-Handshake".getBytes());
		dis.read(buf); // JWDP-Handshake
	}

	// loops indefinitely accepting packages from front-end
	private boolean exitRequested = false;
	public void mainLoop() throws IOException {
		handshake();
		dos.writeEvent(new JDWPVMStartEvent());

		while (!exitRequested) {
			JDWPRequestHeader hdr = dis.readPacket();
			JDWPCommandHandler handler = dispatchHandler(hdr);

			if (handler == null) {
				System.out.println("Unhandled packet " + hdr.toString());
			} else {
				handler.handleCommand(hdr, dis, dos);
			}
		}
	}
	
	// pushes the events to the front-end
	public void sendEvents(JDWPEvent[] events) throws IOException{
		dos.writeEvents(events);
	}

	// wait for a command that cannot be handled internally & forward to the VM
	public VMCommand waitForCommand() throws IOException{
		VMCommand cmd = null;
		
		while (cmd == null){
			JDWPRequestHeader hdr = dis.readPacket();
			JDWPCommandHandler handler = dispatchHandler(hdr);
	
			if (handler != null) {
				// If cmd is null, it means that the handler, have done its job
				// without the need of the VM. Loop again.
				cmd = handler.handleCommand(hdr, dis, dos);
			} else {
				System.out.println("Unhandled packet " + hdr.toString());
			}
		}
		return cmd;
	}
	
	
	public static void main(String args[]) throws Exception {
		JDPABackEnd be = new JDPABackEnd(8000);
		be.mainLoop();
	}
}
